﻿using System;
using System.Threading;
using System.Threading.Tasks;

namespace ServerUtils
{
    /// <summary>
    /// Multiple retries invoker
    /// </summary>
    public class MRInvoker
    {
        private int retryInterval;
        private int maxRetry;

        public event EventHandler<InvokerErrorEventArgs> Error;

        public MRInvoker(int retryInterval, int maxRetry)
        {
            this.retryInterval = retryInterval;
            this.maxRetry = maxRetry;
        }

        public void Invoke(Action action)
        {
            int count = 1;
            while (true)
            {
                try
                {
                    action.Invoke();
                    return;
                }
                catch (Exception ex)
                {
                    bool exceptionHandled = this.ProcessException(ex, count);
                    if (!exceptionHandled)
                    {
                        throw;
                    }
                }

                if (count == this.maxRetry)
                {
                    break;
                }

                Thread.Sleep(this.retryInterval);
                count++;
            }

            throw new Exception("Maximum number of retries");
        }

        public TResult Invoke<TResult>(Func<TResult> func)
        {
            int count = 1;
            while (true)
            {
                try
                {
                    return func.Invoke();
                }
                catch (Exception ex)
                {
                    bool exceptionHandled = this.ProcessException(ex, count);
                    if (!exceptionHandled)
                    {
                        throw;
                    }
                }

                if (count == this.maxRetry)
                {
                    break;
                }

                Thread.Sleep(this.retryInterval);
                count++;
            }

            throw new Exception("Maximum number of retries");
        }

        public async Task InvokeAsync(Func<Task> asyncAction)
        {
            int count = 1;
            while (true)
            {
                try
                {
                    await asyncAction.Invoke();
                    return;
                }
                catch (Exception ex)
                {
                    bool exceptionHandled = this.ProcessException(ex, count);
                    if (!exceptionHandled)
                    {
                        throw;
                    }
                }

                if (count == this.maxRetry)
                {
                    break;
                }

                Thread.Sleep(this.retryInterval);
                count++;
            }

            throw new Exception("Maximum number of retries");
        }

        public async Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> asyncFunc)
        {
            int count = 1;
            while (true)
            {
                try
                {
                    return await asyncFunc.Invoke();
                }
                catch (Exception ex)
                {
                    bool exceptionHandled = this.ProcessException(ex, count);
                    if (!exceptionHandled)
                    {
                        throw;
                    }
                }

                if (count == this.maxRetry)
                {
                    break;
                }

                Thread.Sleep(this.retryInterval);
                count++;
            }

            throw new Exception("Maximum number of retries");
        }

        protected void OnError(InvokerErrorEventArgs e)
        {
            var handler = this.Error;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        private bool ProcessException(Exception ex, int count)
        {
            var ea = new InvokerErrorEventArgs(ex, count);
            this.OnError(ea);
            return ea.ExceptionHandled;
        }
    }
}
